import { reactive } from "../reactivity/reactive";
import { effect } from "../reactivity/effect.js";
import { hasPropsChanged, resolveProps } from "./utils.js";

// 任务队列，用来去重任务
const queue = new Set()

// 任务是否正在执行
let isFlushing = false

// 创建一个 resolve 的promise 实例
const p = Promise.resolve()

// 用来存储正在被初始化的组件实例
let currentInstance = null

// 设置正在初始化的组件
function setCurrentInstance(instance) {
    currentInstance = instance
}

// onMounted 的实现
export function onMounted(fn) {
    if(currentInstance) {
        currentInstance.mounted.push(fn)
    } else {
        console.warn('onMounted 只能在组件中使用')
    }
}

function queueJob(job) {

    queue.add(job)

    if (!isFlushing) {
        isFlushing = true
        p.then(() => {
            try {
                queue.forEach(job => job())
            } finally {
                isFlushing = false
                queue.clear() // 清空任务
            }
        })
    }
}

export function createRenderer(options) {

    const {
        createElement,
        setElementText,
        insert
    } = options

    function patchElement(n1, n2) {
        const el = n2.el = n1.el

        const oldProps = n1.props
        const newProps = n2.props
        for(const key in newProps) {
            if(newProps[key] !== oldProps[key]) {
                patchProps(el, key, oldProps[key], newProps[key])
            }
        }

        for (const key in oldProps) {
            if(!(key in newProps)) {
                patchProps(el, key, oldProps[key], null)
            }
        }
        patchChildren(n1, n2, el)
    }

    function patchChildren(n1, n2, container) {
        if(typeof n2.children === 'string') {
            if(Array.isArray(n1.children)) {
                n1.children.forEach(item => unMount(item))
            }
            setElementText(container, n2.children)
        } else if(Array.isArray(n2.children)) {
            if(Array.isArray(n1.children)) {
                n1.children.forEach(item => unMount(item))
                n2.children.forEach(item => patch(null, item, container))
            } else {
                setElementText(container, '')
                n2.children.forEach(item => patch(null, item, container))
            }
        } else {
            if (Array.isArray(n1.children)) {
                n1.children.forEach(item => unMount(item))
            } else if(typeof n1.children === 'string') {
                setElementText(container, '')
            }
        }
    }

    function patchProps(el, key, prevValue, nextValue) {
        if(/^on/.test(key)) {
            const invokers = el._vei || (el._vei = {})
            const event = key.slice(2).toLowerCase()
            let invoker = invokers[key]
            if(nextValue) {
                if(!invoker) {
                    invoker = el._vei[key] = (e) => {
                        if(Array.isArray(nextValue)) {
                            nextValue.forEach(fn => fn(e))
                        } else {
                            invoker.value(e)
                        }
                    }
                    invoker.value = nextValue
                    el.addEventListener(event, invoker)
                } else {
                    invoker.value = nextValue
                }
            } else if(invoker) {
                el.removeEventListener(event, invoker)
            }
        } else if(key === 'class') {
            el.className = nextValue || ''
        } else if(key in el && (key!=='form' && el.tagName !== 'INPUT')) { //dom属性存在
            let type = typeof el[key]
            let value = typeof nextValue

            if(type==='boolean' && value === '') {
                el[key] = true
            } else {
                el[key] = false
            }

        } else {
            el.setAttribute(key, nextValue)
        }
    }

    function unMount(container) {
        const el = container.el
        const parent = el.parentNode
        if(parent) {
            parent.removeChild(el)
        }
    }

    function mountElement(vnode, container) {
        const el = createElement(vnode.type)
        vnode.el = el
        if(typeof vnode.children === 'string') {
            setElementText(el, vnode.children)
        }
        if(Array.isArray(vnode.children)) {
            vnode.children.forEach(item => {
                patch(null, item, el)
            })
        }

        if(vnode.props) {
            for(let key in vnode.props) {
                patchProps(el, key, null, vnode.props[key])
            }

        }

        insert(el, container)

    }

    function mountComponent(vnode, container, anchor) {

        let setupState = null

        const componentOptions = vnode.type

        let { render, data, setup } = componentOptions

        const { beforeCreate, created, beforeMount, mounted, beforeUpdate, updated  } = componentOptions

        const { props: propsOption } = componentOptions

        beforeCreate && beforeCreate()

        const state = reactive(data())

        const [props, attrs] = resolveProps(propsOption, vnode.props)

        const slots = vnode.children || {}

        const instance = {
            state,
            props: reactive(props),
            isMounted: false,
            subTree: null,
            slots,
            mounted: []
        }

        function emit(event, ...payload) {
            const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
            const handler = instance.props[eventName]
            if(handler) {
                handler(...payload)
            } else {
                console.error(`事件${event}不存在`)
            }
        }

        vnode.component = instance

        const renderContext = new Proxy(instance, {
            get(t,k,r) {
                const { state, props, slots } = t
                if(k === '$slots') {
                    return slots
                }
                if(state && k in state) {
                    return state[k]
                } else if(k in props) {
                    return props[k]
                } else if (setupState && k in setupState) {
                    return setupState[k]
                }
                else {
                    console.error(`属性${k}不存在`)
                }
            },
            set(t,k,v,r) {
                const { state, props } = t
                if(state && k in state) {
                    state[k] = v
                } else if(k in props) {
                    console.error(`属性${k}是只读的`)
                } else if(setupState && k in setupState) {
                    setupState[k] = v
                }
                else {
                    console.error(`属性${k}不存在`)
                }
            }
        })

        // 拿到响应式数据之后调用created
        created && created.call(renderContext)

        //setup 初始化开始前，设置这个组件正在被初始化
        setCurrentInstance(instance)

        // setup 函数实现
        const setupContext = { attrs, emit, slots }

        const setupResult = setup(reactive(instance.props), setupContext)

        // setup 初始化完成，重置组件状态
        setCurrentInstance(null)

        if(typeof setupResult === 'function') {
            render = setupResult
        } else {
            setupState = setupResult
        }

        effect(() => {
            const subTree = render.call(renderContext, renderContext)
            if(!instance.isMounted) {

                beforeMount && beforeMount.call(state)

                patch(null, subTree, container, anchor)
                instance.isMounted = true
                instance.mounted && instance.mounted.forEach(hook => hook.call(state))
                mounted && mounted.call(state)
            } else {
                beforeUpdate && beforeUpdate.call(state)
                patch(instance.subTree, subTree, container, anchor)
                updated && updated.call(state)
            }
            instance.subTree = subTree
        }, {
            scheduler: queueJob
        })
    }

    function patchComponent(n1, n2, anchor) {


        const instance = n2.component = n1.component
        const { props } = instance

        if(hasPropsChanged(n1.props, n2.props)) {

        }

    }


    function patch(n1, n2, container) {
        if (n1 && n1.type !== n2.type) {
            unMount(n1)
            n1 = null
        }
        const { type } = n2
        if (typeof type === 'string') {
            if (!n1) {
                mountElement(n2, container)
            } else {
                patchElement(n1, n2)
            }
        } else if (typeof type === 'object') {
            if(!n1) {
                mountComponent(n2, container, null)
            } else {
                patchComponent(n1, n2, null)
            }

        }
    }

    function render(vnode, container) {

        if(vnode) {
            patch(container._vnode, vnode, container)
        } else {

            if(container._vnode) {
                unMount(container)
            }
        }
        container._vnode = vnode
    }

    return render
}
